#include "util.h"

#ifdef WIN32
#include <Winsock2.h>
#else
#include <endian.h>
#endif

#include <random>
#include <sstream>

#include <cppconn/statement.h>

#include "log/log.h"

time_t getCurrentSecond() { return time(0); }

uint64_t hton64(uint64_t n)
{
#ifdef WIN32
    return htonll(n);
#else
    return htobe64(n);
#endif
}

uint64_t ntoh64(uint64_t n)
{
#ifdef WIN32
    return ntohll(n);
#else
    return be64toh(n);
#endif
}

bool isSupportFieldType(const google::protobuf::Reflection* ref,
                        const google::protobuf::Message* msg,
                        const google::protobuf::FieldDescriptor* field)
{
    switch (field->cpp_type())
    {
    case google::protobuf::FieldDescriptor::CPPTYPE_INT32:
    case google::protobuf::FieldDescriptor::CPPTYPE_INT64:
    case google::protobuf::FieldDescriptor::CPPTYPE_UINT32:
    case google::protobuf::FieldDescriptor::CPPTYPE_UINT64:
    case google::protobuf::FieldDescriptor::CPPTYPE_DOUBLE:
    case google::protobuf::FieldDescriptor::CPPTYPE_FLOAT:
    case google::protobuf::FieldDescriptor::CPPTYPE_BOOL:
    case google::protobuf::FieldDescriptor::CPPTYPE_STRING:
        return true;

    default:
        return false;
    }

    return false;
}

std::string getFieldAsString(const google::protobuf::Reflection* ref,
                             const google::protobuf::Message* msg,
                             const google::protobuf::FieldDescriptor* field)
{
    switch (field->cpp_type())
    {
    case google::protobuf::FieldDescriptor::CPPTYPE_INT32:
        return std::to_string(ref->GetInt32(*msg, field));

    case google::protobuf::FieldDescriptor::CPPTYPE_INT64:
        return std::to_string(ref->GetInt64(*msg, field));

    case google::protobuf::FieldDescriptor::CPPTYPE_UINT32:
        return std::to_string(ref->GetUInt32(*msg, field));

    case google::protobuf::FieldDescriptor::CPPTYPE_UINT64:
        return std::to_string(ref->GetUInt64(*msg, field));

    case google::protobuf::FieldDescriptor::CPPTYPE_DOUBLE:
        return std::to_string(ref->GetDouble(*msg, field));

    case google::protobuf::FieldDescriptor::CPPTYPE_FLOAT:
        return std::to_string(ref->GetFloat(*msg, field));

    case google::protobuf::FieldDescriptor::CPPTYPE_BOOL:
        return std::to_string(ref->GetBool(*msg, field));

    case google::protobuf::FieldDescriptor::CPPTYPE_STRING:
    {
        std::string ret("'");
        ret += ref->GetString(*msg, field);
        ret += "'";
        return ret;
    }

    default:
        return "";
    }
    return "";
}

bool setProtoField(const google::protobuf::Reflection* ref,
                   google::protobuf::Message* msg,
                   const google::protobuf::FieldDescriptor* field,
                   const std::unique_ptr<sql::ResultSet>& rst)
{
    switch (field->cpp_type())
    {
    case google::protobuf::FieldDescriptor::CPPTYPE_INT32:
        ref->SetInt32(msg, field, rst->getInt(field->name()));
        break;

    case google::protobuf::FieldDescriptor::CPPTYPE_INT64:
        ref->SetInt64(msg, field, rst->getInt64(field->name()));
        break;

    case google::protobuf::FieldDescriptor::CPPTYPE_UINT32:
        ref->SetUInt32(msg, field, rst->getUInt(field->name()));
        break;

    case google::protobuf::FieldDescriptor::CPPTYPE_UINT64:
        ref->SetUInt64(msg, field, rst->getUInt64(field->name()));
        break;

    case google::protobuf::FieldDescriptor::CPPTYPE_DOUBLE:
        ref->SetDouble(msg, field, rst->getDouble(field->name()));
        break;

    case google::protobuf::FieldDescriptor::CPPTYPE_FLOAT:
        ref->SetFloat(msg, field, rst->getDouble(field->name()));
        break;

    case google::protobuf::FieldDescriptor::CPPTYPE_BOOL:
        ref->SetBool(msg, field, rst->getBoolean(field->name()));
        break;

    case google::protobuf::FieldDescriptor::CPPTYPE_STRING:
        if (field->type() == google::protobuf::FieldDescriptor::TYPE_STRING)
        {
            ref->SetString(msg, field, rst->getString(field->name()));
        }
        else
        {
            std::istream* is = rst->getBlob(field->name());
            is->seekg(0, is->end);
            std::size_t size = is->tellg();
            is->seekg(0, is->beg);
            std::string buf;
            buf.resize(size);
            char* begin = &*buf.begin();
            is->read(begin, size);
            // is->close();
            delete is;
            ref->SetString(msg, field, buf);
        }
        break;

    default:
        return false;
    }
    return true;
}

bool initProtoDataFromDb(const std::shared_ptr<sql::Connection>& conn, google::protobuf::Message* msg)
{
    auto desc = msg->GetDescriptor();
    auto ref = msg->GetReflection();
    std::string tableName = desc->name();
    // select a, b, c from table where a = 1 and b = 2

    std::stringstream ss, ssw;
    ss << "select ";
    ssw << " where ";
    bool first = true, firstw = true;
    for (int i = 0; i < desc->field_count(); ++i)
    {
        auto field = desc->field(i);
        if (!first)
        {
            ss << " , ";
        }
        ss << "`" << field->name() << "` ";
        first = false;
        if (!field->is_required())
        {
            continue;
        }
        if (!firstw)
        {
            ssw << " and ";
        }
        ssw << "`" << field->name() << "` = " << getFieldAsString(ref, msg, field);
        firstw = false;
    }
    ss << " from " << tableName << " " << ssw.str();
    LOG_DEBUG("Execute sql:" << ss.str());
    std::unique_ptr< sql::Statement > stmt(conn->createStatement());
    std::unique_ptr<sql::ResultSet> rst(stmt->executeQuery(ss.str()));
    if (rst->rowsCount() != 1)
    {
        LOG_ERROR("Init proto data result count not 1 is: " << rst->rowsCount());
        return false;
    }
    rst->first();
    for (int i = 0; i < desc->field_count(); ++i)
    {
        auto field = desc->field(i);
        setProtoField(ref, msg, field, rst);
    }
    return true;
}
